package com.young.ums.web;

import com.young.common.client.ums.UmsClientHelper;
import com.young.common.util.*;
import com.young.ums.model.User;
import com.young.ums.model.*;
import com.young.ums.service.ILoginLogService;
import com.young.ums.service.ISsoSessionService;
import com.young.ums.service.IUmsConfigService;
import com.young.ums.service.IUserService;
import com.young.ums.util.SecureHandler;
import com.young.ums.util.UmsConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.annotation.Resource;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;

/**
 * 单点登录服务
 * @author imrookie
 * @date 2018/9/15
 */
@Controller
@RequestMapping("/sso")
public class SsoController {

    private final Logger logger = LoggerFactory.getLogger(SsoController.class);

    @Resource(name="umsConfigService")
    IUmsConfigService configService;

    @Resource(name="userService")
    IUserService userService;

    @Resource(name="ssoSessionService")
    ISsoSessionService ssoSessionService;

    @Resource(name="loginLogService")
    ILoginLogService loginLogService;

    /**
     * 登录-post方式
     * @param request
     * @param response
     * @return
     */
    @RequestMapping(value = "/login", method = RequestMethod.POST)
    @ResponseBody
    public Object login(HttpServletRequest request,HttpServletResponse response) {
        String account = request.getParameter("account");//用户名
        String pwd = request.getParameter("password");//密码
        String clientIp = ClientInfoUtil.getClientIpAddr(request);//客户端ip地址
        try{
            String token = null;//令牌
            com.young.interfaces.ums.model.User loginUser = UmsClientHelper.getLoginUser();//已登陆用户
            if (loginUser != null) {
                token = loginUser.getToken();
                logger.info("[SSO单点登录] 检测到该会话内已存在登录用户{},跳过验证！", loginUser);
            } else {
                if (StringUtils.isNotBlank(account) && StringUtils.isNotBlank(pwd)) {
                    //对用户的IP进行过滤
                    String blacklist = configService.getUsableConfigValue(UmsConstants.UmsConfigKey.LOGIN_IP_BLACK_LIST);//从配置项中拉取黑名单
                    if (blacklist != null && StringUtils.isNotBlank(blacklist)){
                        if (!this.verifyIp(clientIp, blacklist)){//ip验证不通过
                            writeLoginLog(account, UmsConstants.LoginLogResult.NO, "IP黑名单", clientIp, null);//记录登录日志
                            return SsoResult.buildFail("登录失败");
                        }
                    }

                    User user = userService.getUserByAccount(account);
                    if(user == null){
                        writeLoginLog(account, UmsConstants.LoginLogResult.NO, "用户名不存在", clientIp, null);//记录登录日志
                        return SsoResult.buildFail("用户名不存在");
                    }
                    if(!verifyUser(account,pwd,user)){//验证用户
                        logger.info("[SSO单点登录] 用户名或密码错误,account={},password={}", account, pwd);
                        writeLoginLog(account, UmsConstants.LoginLogResult.NO, "用户名或密码错误", clientIp, null);//记录登录日志
                        return SsoResult.buildFail("用户名或密码错误");
                    }else{
                        //验证成功,生成jwt令牌
                        user.setPassword("");//清空密码
                        //fillUser(user);//查询登录用户的详细信息

                        //为用户发放令牌,令牌信息通过SecureHandler类生成
                        int validTime = UmsConstants.DEFAULT.TOKEN_VALID_TIMES;//默认有效时间
                        try{
                            String validTimeCfg = configService.getUsableConfigValue(UmsConstants.UmsConfigKey.TOKEN_VALID_TIME);//令牌有效期配置
                            if (StringUtils.isNotBlank(validTimeCfg)){
                                validTime = Integer.valueOf(validTimeCfg);
                            }
                        }catch (Exception e){
                            logger.error("[SSO单点登录] 设置TOKEN有效时间发生异常,降级为使用默认值:{}", validTime, e);
                        }

                        //生产jwt
                        SsoJWT ssoJWT = SecureHandler.generateJWT(user, validTime, configService.getUsableConfigValue(UmsConstants.UmsConfigKey.SSO_HS256_KEY));
                        //签发jwt
                        token = SecureHandler.signJWT(ssoJWT, configService.getUsableConfigValue(UmsConstants.UmsConfigKey.SSO_RSA_KEY_PUBLIC));
                        user.setToken(token);

                        //写入cookie
                        Cookie cookie = new Cookie(UmsConstants.SSO.TOKEN_KEY, user.getToken());
                        cookie.setDomain(configService.getUsableConfigValue(UmsConstants.UmsConfigKey.SSO_COOKIE_DOMAIN));//设置cookie的域,用于前台cookie共享
                        response.addCookie(cookie);

                        //lgLogService.writeLGLog(account, "1", "成功", request);//记录登录日志

                        //记录当前会话
                        SsoSession ssoSession = new SsoSession();
                        ssoSession.setUserId(ssoJWT.getPayload().getUserId());
                        //TODO 临时方案
                        String agent = request.getHeader("User-Agent");
                        if (agent != null && agent.indexOf("android") > -1){
                            agent = "android";
                        }else if (agent != null && agent.indexOf("ios") > -1){
                            agent = "ios";
                        }else if (agent != null && agent.indexOf("windowsphone") > -1){
                            agent = "windowsphone";
                        }else{
                            agent = "h5";
                        }
                        ssoSession.setDeviceType(agent);
                        ssoSession.setDeviceVersion("");
                        ssoSession.setToken(ssoJWT.getPayload().getUuid());//token串经加密后长度为1024,不方便存储,这里存储jwt的uuid
                        ssoSession.setSignTime(ssoJWT.getPayload().getIat());
                        ssoSession.setExpireTime(ssoJWT.getPayload().getExp());
                        ssoSession.setClientIp(clientIp);
                        ssoSession.setServerIp(IPUtil.getLocalIp());
                        ssoSession.setStatus(UmsConstants.SsoSessionStatus.YES);
                        ssoSessionService.insert(ssoSession);

                        logger.info("[SSO单点登录] 用户[" + account + "]登录验证通过:{}", user);

                        writeLoginLog(account, UmsConstants.LoginLogResult.YES, "登录成功", clientIp, ssoSession);//记录登录日志
                        userService.updateLoginTime(user.getId(), ssoSession.getSignTime());//更新登录时间
                    }
                }else{
                    logger.info("[SSO单点登录] 用户名或密码无效,account={},password={}", account, pwd);
                    writeLoginLog(account, UmsConstants.LoginLogResult.NO, "用户名或密码无效", clientIp, null);//记录登录日志
                    return SsoResult.buildFail("用户名或密码无效");
                }
            }
            /**
             * 获取重定向页面
             * 1.从redirectTo参数获取;
             * 2.默认跳转首页;
             */
            String redirectTo = request.getParameter("redirectTo");
            //过滤掉注销地址
            if(redirectTo != null && redirectTo.indexOf("logout") > -1){
                logger.info("[SSO单点登录] 过滤掉redirectTo参数:{}", redirectTo);
                redirectTo = "";
            }
            redirectTo = StringUtils.isNotBlank(redirectTo) ? redirectTo : configService.getUsableConfigValue(UmsConstants.UmsConfigKey.SSO_DEFAULT_REDIRECT_URL);

            return SsoResult.buildSuccess(redirectTo, token);
        }catch (Exception e){
            logger.error("[SSO单点登录] 发生异常,account={},password={}", request.getParameter("account"), request.getParameter("password"), e);
            writeLoginLog(account, UmsConstants.LoginLogResult.NO, "失败", clientIp, null);//记录登录日志
            return SsoResult.buildFail("登录失败，请联系管理员!");
        }
    }

    /**
     * 记录登录日志
     * @param account 账号
     * @param result 登录结果,UmsConstants.LoginLogResult
     * @param remark 说明
     * @param ssoSession 登录成功后的会话信息
     */
    private void writeLoginLog(String account, String result, String remark, String clientIp, SsoSession ssoSession){
        try{
            LoginLog loginLog = new LoginLog();
            loginLog.setId(CommonUtil.getUUID());
            loginLog.setAccount(account);
            loginLog.setResult(result);
            loginLog.setClientIp(clientIp);
            if (ssoSession != null){
                loginLog.setServerIp(ssoSession.getServerIp());
                loginLog.setDeviceType(ssoSession.getDeviceType());
                loginLog.setLoginTime(ssoSession.getSignTime());
            }else{
                loginLog.setServerIp(IPUtil.getLocalIp());
                loginLog.setDeviceType("");
                loginLog.setLoginTime(new Date());
            }
            loginLog.setRemark(remark);
            loginLogService.insert(loginLog);
        }catch (Exception e){
            logger.error("[记录登录日志] 发生异常,账号={},登录结果={}", account, result, e);
        }
    }

    /**
     * token验证
     * 1.获取待验证令牌,从cookie和请求体中获取
     * 2.解析令牌,字符串解析为JWT对象
     * 3.获取服务端保存的令牌信息,验证在服务端是否有效(目前以服务端为准)
     * 4.前面的步骤都成功则返回成功,否则验证失败
     * @param token 令牌,前端参数名为UmsConstants.SSO.TOKEN_KEY
     * @param request
     * @param response
     * @return
     */
    @RequestMapping(value = "/verifyMyToken", method = {RequestMethod.POST, RequestMethod.GET})
    @ResponseBody
    public Object verifyMyToken(@RequestParam(UmsConstants.SSO.TOKEN_KEY)String token, HttpServletRequest request,HttpServletResponse response) {
        Cookie cookie = CookieUtil.getCookie(request, UmsConstants.SSO.TOKEN_KEY);//从cookie中取令牌
        if (cookie != null && StringUtils.isNotBlank(cookie.getValue())){//cookie存在令牌
            if (StringUtils.isNotBlank(token)){//存在入参token
                if (!token.equals(cookie.getValue())){//cookie和传入的不一致,报错
                    logger.warn("[token验证] cookie与请求体不一致,cookie={},传参={}", cookie.getValue(), token);
                    return Result.buildFail();
                }
            }
            token = cookie.getValue();
        }else if (StringUtils.isBlank(token)){//cookie和入参都为空
            logger.warn("[token验证] token令牌为空:cookie与请求体都为空");
            return Result.buildFail("令牌为空");
        }

        //解析令牌
        SsoJWT ssoJWT = null;
        try {
            ssoJWT = SecureHandler.parseJWT(token, configService.getUsableConfigValue(UmsConstants.UmsConfigKey.SSO_RSA_KEY_PRIVATE), configService.getUsableConfigValue(UmsConstants.UmsConfigKey.SSO_HS256_KEY));
        } catch (Exception e) {
            logger.error("[token验证] 令牌解析失败,令牌={}", token, e);
            return Result.buildFail();
        }

        //查询服务端回话信息,根据jwt的uuid
        SsoSession ssoSession = ssoSessionService.getByToken(ssoJWT.getPayload().getUuid());
        if (ssoSession == null){
            logger.error("[token验证] 根据令牌UUID[{}]在服务端查找失败,用户id为:{}", ssoJWT.getPayload().getUuid(), ssoJWT.getPayload().getUserId());
            return Result.buildFail();
        }

        if (UmsConstants.SsoSessionStatus.NO == ssoSession.getStatus()){//状态为禁用
            logger.warn("[token验证] 令牌[UUID={}]状态为禁用,用户id为:{}", ssoJWT.getPayload().getUuid(), ssoJWT.getPayload().getUserId());
            return Result.buildFail();
        }

        if (ssoSession.getExpireTime().before(new Date())){//已经过期
            logger.info("[token验证] 令牌已过期,令牌UUID={},用户id={}", ssoJWT.getPayload().getUuid(), ssoJWT.getPayload().getUserId());
            return Result.buildFail("会话已过期");
        }

        return Result.buildSuccess();
    }

    /**
     * 验证用户
     * 验证规则:
     * 1.数据库中存在对于账号的用户信息;
     * 2.用户状态为可用;
     * 3.输入的账号密码与数据库查询到的一致;
     * @param account 用户输入的账号
     * @param password 用户输入的密码
     * @param user 数据库中查询到的用户信息
     * @return
     */
    private boolean verifyUser(String account,String password,User user){
        try {
            if(user != null && UmsConstants.UserState.YES == user.getState() && SecureHandler.encryptPwd(password).equals(user.getPassword()) && account.equals(user.getAccount())){//密码匹配验证
                return true;
            }
        } catch (Exception e) {
            logger.error("[SSO单点登录] 用户验证阶段发生异常,用户信息:{}", user, e);
        }
        return false;
    }

    /**
     * 验证ip
     * @param ip 待验证的ip
     * @param blacklist 黑名单
     * @return
     */
    private boolean verifyIp(String ip, String blacklist){
        if (StringUtils.isNotBlank(ip) && StringUtils.isNotBlank(blacklist)){
            String[] ips = blacklist.split(";");
            if (ips != null && ips.length > 0){
                for (String black : ips){
                    black = black.trim();//去除空白
                    if (StringUtils.isNotBlank(black)){
                        String[] blackItems = black.split("\\.");
                        String[] ipItems = ip.split("\\.");
                        if (blackItems.length != 0 && blackItems.length == ipItems.length){//ip格式相同
                            boolean flag = true;
                            for (int i=0;i<blackItems.length;i++){
                                if ("*".equals(blackItems[i]) || StringUtils.equals(blackItems[i], ipItems[i])){//通配符配置，该段忽略//该段相等，则继续下一段匹配
                                    continue;
                                } else {//不相等，则跳出匹配，进行下一个黑ip的匹配
                                    flag = false;
                                    break;
                                }
                            }
                            if (flag){//匹配成功，说明当前IP为黑名单中的IP
                                logger.warn("[SSO单点登录] IP验证失败，可疑的用户IP：{}，对应黑名单IP为：{}", ip, black);
                                //返回验证失败
                                return false;
                            }
                        }
                    }
                }
            }
        }
        return true;
    }

    /**
     * 注销登录
     * @param request
     * @return
     * @throws Exception
     */
    @RequestMapping(value = "/logout", method = RequestMethod.GET)
    public String logout(HttpServletRequest request, HttpServletResponse response){
        com.young.interfaces.ums.model.User user = UmsClientHelper.getLoginUser();
        //重定向到登录页
        String redirectTo = request.getParameter("redirectTo");
        if (StringUtils.isBlank(redirectTo)){
            redirectTo = CommonUtil.getDefaultValue(configService.getUsableConfigValue(UmsConstants.UmsConfigKey.SSO_LOGOUT_REDIRECT_TO), UmsConstants.SSO.REDIRECT_LOGIN);
        }
        if(user != null){
            logger.info("[注销登录] {}[账号:{}] 退出系统,重定向地址:{}", user.getName(), user.getAccount(), redirectTo);
        }else{
            logger.info("[注销登录] 当前会话不存在用户信息,重定向地址:{}", redirectTo);
        }
        //清空session
        request.getSession().invalidate();
        //清除token信息
        Cookie cookie = new Cookie(UmsConstants.SSO.TOKEN_KEY, null);
        cookie.setDomain(configService.getUsableConfigValue(UmsConstants.UmsConfigKey.SSO_COOKIE_DOMAIN));
        cookie.setMaxAge(0);
        response.addCookie(cookie);
        return "redirect:" + redirectTo;
    }
}
